package com.bifang.module.file.core.util;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.*;

import cn.hutool.extra.servlet.ServletUtil;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.OSSObject;
import com.bifang.engine.assistant.core.enums.BoolEnum;
import com.bifang.engine.assistant.core.exception.AppException;
import com.bifang.module.file.common.enums.ShowFileEnum;
import com.bifang.module.file.common.util.ContentType;
import com.bifang.module.file.common.vo.FileClassVO;
import com.bifang.module.file.common.vo.FileInfoVO;
import com.bifang.module.file.common.util.Encode;
import com.bifang.module.file.dao.model.FileAlioss;
import com.bifang.module.file.dao.service.FileAliossService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Date;

@Component
public class AliOssUtil {
    protected Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired private FileCommonUtil fileCommonUtil;

    @Value("${aliyun.oss.endpoint}")
    private String defaultEndpoint;

    @Value("${aliyun.oss.accessKeyId}")
    private String defaultAccessKeyId;

    @Value("${aliyun.oss.accessKeySecret}")
    private String defaultAccessKeySecret;

    @Value("${aliyun.oss.bucketName}")
    private String defaultBucketName;

    @Value("${defaultFilePath}")
    private String defaultFilePath;

    @Autowired FileAliossService fileAliossService;

    public Boolean upload(MultipartFile file, FileClassVO fileClassVO, FileInfoVO fileInfoVO) {
        InputStream inputStream = null;
        try {
            inputStream = file.getInputStream();
            return this.upload(inputStream, fileClassVO, fileInfoVO);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public Boolean upload(File file, FileClassVO fileClassVO, FileInfoVO fileInfoVO) {
        InputStream inputStream = null;
        try {
            inputStream = FileUtil.getInputStream(file);
            return this.upload(inputStream, fileClassVO, fileInfoVO);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public Boolean upload(byte[] file, FileClassVO fileClassVO, FileInfoVO fileInfoVO) {
        return this.upload(new ByteArrayInputStream(file), fileClassVO, fileInfoVO);
    }

    public Boolean upload(InputStream in, FileClassVO fileClassVO, FileInfoVO fileInfoVO) {
        if (fileClassVO == null) {
            throw new AppException("文件分类信息不能为空");
        }
        if (fileInfoVO == null) {
            throw new AppException("文件信息不能为空");
        }
        Boolean flag = true;
        fileCommonUtil.initFile(in, fileClassVO, fileInfoVO);
        // 校验文件是否可以上传
        fileCommonUtil.checkFile(fileInfoVO.getFileSuffix(), fileInfoVO.getFileSize(), fileClassVO);
        if (StringUtils.isNotBlank(fileInfoVO.getEncryptionFlag())) {
            if (BoolEnum.TRUE.getType().endsWith(fileInfoVO.getEncryptionFlag())) {
                String fileName = this.getFileName(fileInfoVO);
                File file = fileCommonUtil.createTempFile(in, fileName);
                fileInfoVO.setFilePassowrd(RandomUtil.randomString(5));
                new Encode(true, file, fileInfoVO.getFilePassowrd()).run();
                InputStream inputStream = null;
                try {
                    inputStream = FileUtil.getInputStream(file);
                } catch (Exception e) {
                    e.printStackTrace();
                    flag = false;
                } finally {
                    if (inputStream != null) {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                if (flag) {
                    flag = uploadFile(inputStream, fileClassVO, fileInfoVO);
                }
                fileCommonUtil.delTempFile(file);
                return flag;
            }
        } else {
            fileInfoVO.setEncryptionFlag(BoolEnum.FALSE.getType());
        }
        flag = uploadFile(in, fileClassVO, fileInfoVO);
        return flag;
    }

    private String getFileName(FileInfoVO fileInfoVO) {
        return fileInfoVO.getFileMd5() + "." + fileInfoVO.getFileSuffix();
    }

    public Boolean uploadFile(InputStream in, FileClassVO fileClassVO, FileInfoVO fileInfoVO) {
        String bucketName = fileInfoVO.getStorageGroup();
        if (StringUtils.isBlank(bucketName)) {
            bucketName = defaultBucketName;
        }
        String basePath = fileInfoVO.getFileBasePath();
        if (StringUtils.isBlank(basePath)) {
            if(StringUtils.isBlank(defaultFilePath)){
                basePath = "baseFile";
            }else{
                basePath=defaultFilePath;
            }
        }
        String classPath = fileClassVO.getFileClassPath();
        String fileName = IdUtil.fastSimpleUUID() + "." + fileInfoVO.getFileSuffix();
        Boolean flag = true;
        OSS ossClient = this.init(fileInfoVO);
        // 创建OSSClient实例
        String path = fileCommonUtil.getPath(basePath, classPath, fileName);
        try {
            fileInfoVO.setOtherStartTime(new Date());
            ossClient.putObject(bucketName, path, in);
            fileInfoVO.setOtherEndTime(new Date());
        } catch (Exception e) {
            logger.error("文件上传失败", e);
            flag = false;
        } finally {
            // 关闭client
            ossClient.shutdown();
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        fileInfoVO.setFilePath(path);
        return flag;
    }
    /** 初始化 */
    private OSS init(FileInfoVO fileInfoVO) {
        String endpoint = fileInfoVO.getFileServiceIp();
        String accessKeyId = fileInfoVO.getFileServiceUser();
        String accessKeySecret = fileInfoVO.getFileServicePwd();
        if (StringUtils.isBlank(endpoint)) {
            endpoint = defaultEndpoint;
        }
        if (StringUtils.isBlank(accessKeyId)) {
            accessKeyId = defaultAccessKeyId;
        }
        if (StringUtils.isBlank(accessKeySecret)) {
            accessKeySecret = defaultAccessKeySecret;
        }

        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        return ossClient;
    }
    /** 获取base64文件 */
    public String getBase64(FileInfoVO fileInfoVO) {
        fileCommonUtil.checkBase64(fileInfoVO.getFileSize());
        String fileName = fileInfoVO.getFileMd5() + "." + fileInfoVO.getFileSuffix();
        if (fileInfoVO.getForceFlag()) {
            fileCommonUtil.delTempFileDownloadByName(fileName);
        }
        File file = fileCommonUtil.createTempFileDownload(fileName);
        if (file.length() <= 0) {
            // 空文件重新下载
            this.download(fileInfoVO, file);
            if (BoolEnum.TRUE.getType().equals(fileInfoVO.getEncryptionFlag())) {
                new Encode(false, file, fileInfoVO.getFilePassowrd()).run();
            }
        }
        String base64 = Base64.encode(file);
        FileUtil.del(file);
        return base64;
    }
    /** 根据key删除OSS服务器上的文件 */
    public void deleteFile(FileInfoVO fileInfoVO) {
        String bucketName = fileInfoVO.getStorageGroup();
        if (StringUtils.isBlank(bucketName)) {
            bucketName = defaultBucketName;
        }
        String path = fileInfoVO.getFilePath();
        OSS ossClient = this.init(fileInfoVO);
        ossClient.deleteObject(bucketName, path);
        logger.info("删除" + bucketName + "下的文件" + path + "成功");
    }
    /**
     * 判断文件是否存在。doesObjectExist还有一个参数isOnlyInOSS， 如果为true则忽略302重定向或镜像；如果为false，则考虑302重定向或镜像。
     * yourObjectName 表示上传文件到OSS时需要指定包含文件后缀在内的完整路径，例如abc/efg/123.jpg。
     *
     * @return 存在返回true
     */
    public boolean doesObjectExist(FileInfoVO fileInfoVO) {
        String bucketName = fileInfoVO.getStorageGroup();
        if (StringUtils.isBlank(bucketName)) {
            bucketName = defaultBucketName;
        }
        String path = fileInfoVO.getFilePath();
        OSS ossClient = this.init(fileInfoVO);
        boolean exists = ossClient.doesObjectExist(bucketName, path);
        return exists;
    }
    /**
     * 获得url链接
     *
     * @return
     */
    public String getUrl(FileInfoVO fileInfoVO) {
        FileAlioss fileAlioss = fileAliossService.getById(fileInfoVO.getShowId());
        String fileUrl = "";
        if (fileAlioss == null) {
            fileUrl = this.getAliOssUrl(fileInfoVO);
        } else {
            long time = System.currentTimeMillis();
            long expireTime = Long.parseLong(fileAlioss.getExpireTime().toString());
            long exTime = new Date(fileAlioss.getCreateTime().getTime() + expireTime).getTime();
            if ( exTime-time < 1800000) {
                fileUrl = this.getAliOssUrl(fileInfoVO);
            } else {
                fileUrl = fileAlioss.getFileUrl();
            }
        }
        return fileUrl;
    }

    private String getAliOssUrl(FileInfoVO fileInfoVO) {
        // 设置URL过期时间为10年 3600l* 1000*24*365*10
        long expireTime = 3600L * 1000 * 24;
        if (fileInfoVO.getExpireTime() != null
                && fileInfoVO.getExpireTime().compareTo(BigDecimal.ZERO) > 0) {
            expireTime = Long.parseLong(fileInfoVO.getExpireTime().toString()) * 1000;
        }
        fileInfoVO.setExpireTime(new BigDecimal(expireTime));
        Date expiration = new Date(System.currentTimeMillis() + expireTime);
        // 生成URL
        String bucketName = fileInfoVO.getStorageGroup();
        if (StringUtils.isBlank(bucketName)) {
            bucketName = defaultBucketName;
        }
        String path = fileInfoVO.getFilePath();
        OSS ossClient = this.init(fileInfoVO);
        URL url = ossClient.generatePresignedUrl(bucketName, path, expiration);
        if (url != null) {
            FileAlioss fileAlioss = new FileAlioss();
            fileAlioss.setShowId(fileInfoVO.getShowId());
            fileAlioss.setExpireTime(expireTime);
            fileAlioss.setFileUrl(url.toString());
            fileAlioss.setCreateTime(new Date());
            fileAliossService.saveOrUpdate(fileAlioss);
            return url.toString();

        } else {
            throw new AppException("获取云服务文件访问url失败");
        }
    }
    /** 文件下载直接输出返回流 */
    public void download(FileInfoVO fileInfoVO, HttpServletResponse response) {
        String bucketName = fileInfoVO.getStorageGroup();
        if (StringUtils.isBlank(bucketName)) {
            bucketName = defaultBucketName;
        }
        String path = fileInfoVO.getFilePath();
        OSS ossClient = this.init(fileInfoVO);
        boolean exists = ossClient.doesObjectExist(bucketName, path);
        if (!exists) {
            throw new AppException("文件不存在");
        }
        OSSObject ossObject = null;
        try {

            // ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。
            ossObject = ossClient.getObject(bucketName, path);

            InputStream in = ossObject.getObjectContent();
            String fileName = StringUtils.isNotBlank(fileInfoVO.getFileName()) ?
                    URLEncoder.encode(fileInfoVO.getFileName(), "utf-8")
                            .replaceAll("%20", "\\+")
                            .replaceAll("%28", "\\(")
                            .replaceAll("%29", "\\)")
                            .replaceAll("%3B", ";")
                            .replaceAll("%40", "@")
                            .replaceAll("%23", "\\#")
                            .replaceAll("%26", "\\&")
                            .replaceAll("%2C", "\\,")
                            .replaceAll("%24", "\\$")
                            .replaceAll("%25", "\\%")
                            .replaceAll("%5E", "\\^")
                            .replaceAll("%3D", "\\=")
                            .replaceAll("%2B", "\\+") : fileInfoVO.getFileMd5() + "." + fileInfoVO.getFileSuffix();
            String contentType = ContentType.getContentType("." + fileInfoVO.getFileSuffix());
            if (contentType == null) {
                contentType = ContentType.getContentType(fileInfoVO.getFileSuffix());
            }
            response.setCharacterEncoding("utf-8");
            response.setContentType(contentType);
            response.addHeader("Content-Length", "" + fileInfoVO.getFileSize());
            if (ShowFileEnum.getByType(contentType) != null) {
                final String charset = ObjectUtil.defaultIfNull(response.getCharacterEncoding(), CharsetUtil.UTF_8);
                response.setHeader("Content-Disposition", StrUtil.format("inline;filename=\"{}\"",
                        URLUtil.encode(fileName, CharsetUtil.charset(charset))));
            } else {
                response.setContentType("application/octet-stream;charset=utf-8");
                final String charset = ObjectUtil.defaultIfNull(response.getCharacterEncoding(), CharsetUtil.UTF_8);
                response.setHeader("Content-Disposition", StrUtil.format("attachment;filename=\"{}\"",
                        URLUtil.encode(fileName, CharsetUtil.charset(charset))));
            }
            response.setContentLengthLong(fileInfoVO.getFileSize());
            ServletUtil.write(response, in);
        } catch (ClientException e) {
            throw e;
        } catch (OSSException e) {
            throw e;
        }catch (UnsupportedCharsetException e) {
            throw e;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } finally {
            if (ossObject != null) {
                try {
                    ossObject.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
    /** 文件下载 输出到指定路径 */
    public void download(FileInfoVO fileInfoVO, String filePath) {
        File file = FileUtil.touch(filePath);
        this.download(fileInfoVO, file);
    }
    /** 文件下载 输出到指定文件 */
    public void download(FileInfoVO fileInfoVO, File file) {
        String bucketName = fileInfoVO.getStorageGroup();
        if (StringUtils.isBlank(bucketName)) {
            bucketName = defaultBucketName;
        }
        String path = fileInfoVO.getFilePath();
        OSS ossClient = this.init(fileInfoVO);
        boolean exists = ossClient.doesObjectExist(bucketName, path);
        if (!exists) {
            throw new AppException("文件不存在");
        }
        OSSObject ossObject = null;
        InputStream in = null;
        try {
            // ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。
            ossObject = ossClient.getObject(bucketName, path);
            in = ossObject.getObjectContent();
            FileUtil.writeFromStream(in, file);
            if (BoolEnum.TRUE.getType().equals(fileInfoVO.getEncryptionFlag())) {
                new Encode(false, file, fileInfoVO.getFilePassowrd()).run();
            }
        } catch (Exception e) {
            throw e;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ossObject != null) {
                try {
                    ossObject.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    public String getPath(String url) {
        if (StringUtils.isBlank(url)) {
            return "";
        }
        if (url.indexOf("//") > 0) {
            String path = url.substring(url.indexOf("//") + 2);
            path = path.substring(path.indexOf("/"));
            return path;
        } else {
            if (url.startsWith("/")) {
                return url;
            } else {
                return "/" + url;
            }
        }
    }
}
